
`include "common_header.verilog"

//  *************************************************************************
//  File : stats_memarbs
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized Reproduction or Use is Expressly Prohibited.
//  Copyright (c) 2009 MoreThanIP.com, Germany
//  Designed by : Daniel Koehler
//  support@morethanip.com
//  *************************************************************************
//  Description: Arbitrate the memory read and write to support counter
//               clear and allow host read during counter increment idle times.
//  Version    : $Id: stats_memarbs.v,v 1.6 2015/08/20 08:43:49 ts Exp $
//  *************************************************************************

module stats_memarbs (

   reset,
   clk,
   stc_clearread,
   stats_rd,
   stats_sel,
   stats_sel_vld,
   stats_clear,
   stats_busy,
   stat_wait,
   vec_busy_int,
   caddr,
   caddr_val,
   rad_d1,
   rden_d1,
   rden_d2,
   rden_host_d2,
   rad,
   rden,
   rden_host,
   rden_host_x,
   wclear);

parameter CNTNUM  = 32;         // how many counters
parameter CNTADDR = 5;          // address bits required for these

input   reset;                  //  async active high
input   clk;                    // line clock
input   stc_clearread;          //  configuration: clear counter on read
input   stats_rd;               //  read request
input   [CNTADDR-1:0] stats_sel;//  address of counter to read (remapped)
input   stats_sel_vld;          //  indicate if counter address is valid (one of the herein managed counters has been addressed)
input   stats_clear;            //  clear statistics (of all ports)
output  stats_busy;             //  statistics read busy
output  stat_wait;              //  hold statistics counter increment processing due to host access ongoing
input   vec_busy_int;           //  processing is ongoing, asserts following stat_val
input   [CNTADDR - 1:0] caddr;  //  counter address
input   caddr_val;              //  counter address valid, should increment
input   [CNTADDR - 1:0] rad_d1; //  counter address of last data read
input   rden_d1;                //  memory read pipeline last data valid
input   rden_d2;                //  memory read pipeline data valid
input   rden_host_d2;           //  memory read pipeline data valid, host read only
output  [CNTADDR - 1:0] rad;    //  read address
output  rden;                   //  read enable, rad is valid and read/modify/write must happen
output  rden_host;              //  indicate read of host address (omit write)
output  rden_host_x;            //  indicate read of host address overlaps pipeline read, hence capture current data from pipeline
output  wclear;                 //  clear counter (write all zero)

reg     stats_busy; 
reg     stat_wait; 
reg     [CNTADDR - 1:0] rad; 
reg     rden; 
reg     rden_host; 
reg     rden_host_x;
reg     wclear; 

// TYPE stm_typ:
parameter STM_TYP_IDLE          = 4'd 0;
parameter STM_TYP_CINCWAIT      = 4'd 1;
parameter STM_TYP_MCLEAR        = 4'd 2;
parameter STM_TYP_CINC          = 4'd 3;
parameter STM_TYP_HOSTRD        = 4'd 4;
parameter STM_TYP_HOSTRD_WAIT   = 4'd 5;
parameter STM_TYP_CINC_TO_HOST  = 4'd 6;
parameter STM_TYP_HOST_DONE_WAIT= 4'd 7;
parameter STM_TYP_HOSTRD_X      = 4'd 8;

reg     [3:0] state; 
reg     [3:0] nextstate; 
wire    stats_rd_any;           //  read from host, any port, pending
reg     stats_rd_any_d;         //  read from host, any port, pending
reg     stats_rd_req;           //  read from host, any port, pending
reg     [CNTADDR - 1:0] clear_cnt; 
wire    [CNTADDR - 1:0] clear_cnt_max; //  for max compares
reg     clear_done; 

assign stats_rd_any = stats_rd; 

//  STM
//  ---        
always @(posedge reset or posedge clk)
   begin : stmr
   if (reset == 1'b 1)
      begin
      state <= STM_TYP_MCLEAR;	
      end
   else
      begin
      state <= nextstate;	
      end
   end

always @(state or stc_clearread or stats_clear or stats_rd_req or caddr_val or clear_done or vec_busy_int or rden_d2 
         or rden_d1 or rad_d1 or stats_sel)
   begin : stmc
   case (state)
   STM_TYP_IDLE:
      begin
      if (stats_clear == 1'b 1)
         begin
        //  clear all memory
         nextstate = STM_TYP_MCLEAR;	
         end
      else if (caddr_val == 1'b 1 )
         begin
        //  increment counters (sanity here, will always be possible only from CINCWAIT)
         nextstate = STM_TYP_CINC;	
         end
      else if (vec_busy_int == 1'b 1 )
         begin
        //  counter vector is newly loaded, wait until processing starts
         nextstate = STM_TYP_CINCWAIT;	
         end
      else if (stats_rd_req == 1'b 1 )
         begin
         if (stc_clearread == 1'b 1 & rden_d2 == 1'b 1)
            begin
                //  must wait until current counter increment has written back all data 
            nextstate = STM_TYP_HOSTRD_WAIT;	
            end
         else
            begin
                //  host read granted

            if( stc_clearread == 1'b 0 & rden_d1 == 1'b 1 & rad_d1 == stats_sel )
               begin
                // a read to same address as currently in write pipeline occurs. Avoid read/write to same address.
               nextstate = STM_TYP_HOSTRD_X; 
               end 
            else
               begin
               nextstate = STM_TYP_HOSTRD;	
               end


            end
         end
      else
         begin
         nextstate = STM_TYP_IDLE;	
         end
      end

   //  A new counter vector is loaded, wait until its processing starts
   //  -----------------------------------------
   STM_TYP_CINCWAIT:
      begin
      if (stats_clear == 1'b 1)
         begin
         nextstate = STM_TYP_MCLEAR;	
         end
      else if (caddr_val == 1'b 1 )
         begin
        //  increment counters
         nextstate = STM_TYP_CINC;	
         end
      else if (vec_busy_int == 1'b 0 )
         begin
        //  aborted ?
         nextstate = STM_TYP_IDLE;	
         end
      else
         begin
         nextstate = STM_TYP_CINCWAIT;	
         end
      end

   //  clear the complete memory                                
   //  -----------------------------------------
   STM_TYP_MCLEAR:
      begin
      if (clear_done == 1'b 1)
         begin
         nextstate = STM_TYP_IDLE;	
         end
      else
         begin
         nextstate = STM_TYP_MCLEAR;	
         end
      end

   //  increment a bank of counters
   //  -----------------------------------------
   STM_TYP_CINC:
      begin
      if (stats_clear == 1'b 1)
         begin
         nextstate = STM_TYP_MCLEAR;	
         end
      else if (caddr_val == 1'b 1 & stats_rd_req == 1'b 1 & stc_clearread == 1'b 1 & vec_busy_int == 1'b 0 )
         begin
        //  force a host read after current increment processing (clear-after-read only)
         nextstate = STM_TYP_CINC_TO_HOST;	
         end
      else if (caddr_val == 1'b 0 )
         begin
         if (stats_rd_req == 1'b 1 & stc_clearread == 1'b 0)
            begin
        //  directly go to host read following the port processing
            if( rden_d1 == 1'b 1 & rad_d1 == stats_sel )
               begin
                // a read to same address as currently in write pipeline occurs. Avoid read/write to same address.
               nextstate = STM_TYP_HOSTRD_X; 
               end 
            else
               begin
               nextstate = STM_TYP_HOSTRD;	
               end
            end
         else if (stats_rd_req == 1'b 1 & stc_clearread == 1'b 1 & vec_busy_int == 1'b 0 )
            begin
        //  directly go to host read following the port processing (clear-after-read only)
            nextstate = STM_TYP_HOSTRD_WAIT;	
            end
         else
            begin
            nextstate = STM_TYP_IDLE;	
            end
         end
      else
         begin
         nextstate = STM_TYP_CINC;	
         end
      end

   //  Current Memory read is same as host wants to read.
   //  To avoid read/write to same address conflict capture the data from the pipeline
   //  ------------------------------------------------------------------------------------
   STM_TYP_HOSTRD_X:
      begin
      if (caddr_val == 1'b 1 )
         begin
         nextstate = STM_TYP_CINC;	//  as from CINCWAIT
         end
      else if (vec_busy_int == 1'b 1 )
         begin
         nextstate = STM_TYP_CINCWAIT;	//  as from IDLE
         end
      else
         begin
         nextstate = STM_TYP_IDLE;	
         end
      end

   //  create pulse that host address is placed on memory read bus
   //  -----------------------------------------------------------
   STM_TYP_HOSTRD:
      begin
      if (stc_clearread == 1'b 1)
         begin
        //  reading now, but must then wait until the clear has been performed
         nextstate = STM_TYP_HOST_DONE_WAIT;	
         end
      else if (caddr_val == 1'b 1 )
         begin
         nextstate = STM_TYP_CINC;	//  as from CINCWAIT
         end
      else if (vec_busy_int == 1'b 1 )
         begin
         nextstate = STM_TYP_CINCWAIT;	//  as from IDLE
         end
      else
         begin
         nextstate = STM_TYP_IDLE;	
         end
      end

//  additional states for clear-after-read operation
//  ------------------------------------------------

   //  Read after counter processing, need to wait until last counter has been written back.
   //  ------------------------------------------------------------------------------------
   STM_TYP_HOSTRD_WAIT:
      begin
      if (rden_d2 == 1'b 0)
         begin
         nextstate = STM_TYP_HOSTRD;	
         end
      else
         begin
         nextstate = STM_TYP_HOSTRD_WAIT;	
         end
      end

   //  schedule a host read after current increment (clear-after-read only)
   //  --------------------------------------------
   STM_TYP_CINC_TO_HOST:
      begin
      if (caddr_val == 1'b 0)
         begin
         nextstate = STM_TYP_HOSTRD_WAIT;	
         end
      else
         begin
         nextstate = STM_TYP_CINC_TO_HOST;	
         end
      end

   //  wait until the clear after read has been completed (clear-after-read only)
   //  --------------------------------------------------
   STM_TYP_HOST_DONE_WAIT:
      begin
      nextstate = STM_TYP_IDLE;	//  return immediately as caddr_val has a latency of 2 which is same as the read latency so to guarantee no loss of time
      end

   default:
      begin
      nextstate = STM_TYP_IDLE;	//  never reached
      end

   endcase
   end

//  Create address for memory clear
//  -------------------------------

//assign clear_cnt_max = {(CNTADDR){1'b 1}}; 
assign clear_cnt_max = CNTNUM-1; 

always @(posedge reset or posedge clk)
   begin : process_1
   if (reset == 1'b 1)
      begin
      clear_cnt  <= {(CNTADDR){1'b 0}};	
      clear_done <= 1'b 0;	
      end
   else
      begin
      if (state == STM_TYP_MCLEAR)
         begin
         clear_cnt <= clear_cnt + {{(CNTADDR-1){1'b 0}},1'b 1};	
         end
      else
         begin
         clear_cnt <= {(CNTADDR){1'b 0}};	
         end

      if (clear_cnt == clear_cnt_max)
         begin
         clear_done <= 1'b 1;	
         end
      else
         begin
         clear_done <= 1'b 0;	
         end

      end
   end

//  Host read handshaking
//  stats_rd is allowed to stay asserted, hence do a rising edge detection here.
//  -------------------------------
always @(posedge reset or posedge clk)
   begin : process_2
   if (reset == 1'b 1)
      begin
      stats_rd_any_d <= 1'b 0;	
      stats_rd_req   <= 1'b 0;	
      stats_busy     <= 1'b 0;	
      end
   else
      begin

      stats_rd_any_d <= stats_rd_any & stats_sel_vld;	//  react only if it really affect our herein managed counters

      if (stats_sel_vld == 1'b 1 & stats_rd_any == 1'b 1 & stats_rd_any_d == 1'b 0)
         begin
        //  ensure read request only once at rising edge of read request
         stats_rd_req <= 1'b 1;	
         stats_busy <= 1'b 1;	
         end
      else if (state == STM_TYP_HOSTRD )
         begin
         stats_rd_req <= 1'b 0;	
         // stats_busy   <= stats_busy;     -- keep the busy until data is valid at memory output
         end
      else if (rden_host_d2 == 1'b 1 || state == STM_TYP_HOSTRD_X)
         begin
         stats_rd_req <= 1'b 0;	
         stats_busy <= 1'b 0;	//  clear busy (indicating data valid after sampling)
         end
      end
   end

//  delay the processing of a new vector (we can delay only before it started)
//  in case a host read occurs and we want a clear-after-read, as in this case
//  must wait until the write has occured to clean the entry before we can 
//  allow to read it again by the increment processing.
always @(posedge reset or posedge clk)
   begin : process_3
   if (reset == 1'b 1)
      begin
      stat_wait <= 1'b 0;	
      end
   else
      begin
      if ((nextstate == STM_TYP_HOSTRD & stc_clearread==1'b 1) |        // need to wait only when using clear-on-read 
          nextstate == STM_TYP_CINC_TO_HOST | 
          nextstate == STM_TYP_HOSTRD_WAIT | 
          nextstate == STM_TYP_HOST_DONE_WAIT | 
          state     == STM_TYP_MCLEAR )
         begin
         stat_wait <= 1'b 1;	
         end
      else
         begin
         stat_wait <= 1'b 0;	
         end
      end
   end

//  Memory Interface Control
//  ------------------------
always @(posedge reset or posedge clk)
   begin : process_4
   if (reset == 1'b 1)
      begin
      rad       <= {(CNTADDR){1'b 0}};	
      rden      <= 1'b 0;	
      rden_host <= 1'b 0;
      rden_host_x <= 1'b 0;	
      wclear    <= 1'b 0;	
      end
   else
      begin
      if (nextstate == STM_TYP_CINC | nextstate == STM_TYP_CINC_TO_HOST)
         begin
         //  read/modify/write cycles
         // rad             <= caddr;
         rden <= 1'b 1;	
         rden_host <= 1'b 0;	
         wclear <= 1'b 0;	
         end
      else if (state == STM_TYP_HOSTRD )
         begin
         //  nextstate, need to be later if clearread is set
         //  host read 
         // rad             <= stats_sel;
         rden <= 1'b 1;	
         rden_host <= 1'b 1;	//  note: this is in addition to rden
         wclear <= stc_clearread;	//  clear after read
         end
      else if (state == STM_TYP_MCLEAR )
         begin
         //  clear all counters
         // rad             <= clear_cnt;
         rden <= 1'b 0;	
         rden_host <= 1'b 0;	
         wclear <= 1'b 1;	
         end
      else
         begin
         // rad             <= clear_cnt;   -- default to same as clear to save one branch here
         rden <= 1'b 0;	
         rden_host <= 1'b 0;	
         wclear <= 1'b 0;	
         end

        //  the address mux is simpler, avoiding need for nextstate comparison
        
      if (state == STM_TYP_HOSTRD)
         begin
         rad <= stats_sel;	
         end
      else if (state == STM_TYP_MCLEAR )
         begin
         rad <= clear_cnt;	
         end
      else
         begin
         rad <= caddr;	
         end

      // special host read when overlapping with pipeline read

      if( nextstate == STM_TYP_HOSTRD_X )
         begin
         rden_host_x <= 1'b 1;	
         end
      else
         begin
         rden_host_x <= 1'b 0;	
         end

      end

   end

// ----------
// ASSERTIONS
// ----------

`ifdef MTIPASSERTIONS

// synopsys translate_off
// synthesis translate_off

always @(posedge reset or negedge clk)
begin : assertions
        if (reset == 1'b 1)
        begin
        end
        else
        begin
                if( state==STM_TYP_CINCWAIT )
                begin
                        if(! (nextstate!=STM_TYP_IDLE) )
                        begin
                        $display("ASSERT ***[stats_memarbs]: ERROR: New statistics vector arrived while current is in progress. This vector will be lost.");
                        //`ifdef MTIPASSERTIONS_STOP_ON_ERRORS
                        //$stop(1);
                        //`endif
                        end
                end

                if( state==STM_TYP_IDLE )
                begin
                        
                        if(! (nextstate!=STM_TYP_CINC) )
                        begin
                        $display("ASSERT ***[stats_memarbs]: ERROR: statemachine in IDLE, should never have a running increment (caddr_val=1, nextstate=CINC)");
                        `ifdef MTIPASSERTIONS_STOP_ON_ERRORS
                        $stop(1);
                        `endif
                        end
                        
                end
               
                if( state==STM_TYP_HOSTRD )
                begin
                        
                        if(! (stats_clear==1'b 0) )
                        begin
                        $display("ASSERT ***[stats_memarbs]: ERROR: Statistics clear command at same time as host read. Not allowed as it would produce inconsistent read.");
                        `ifdef MTIPASSERTIONS_STOP_ON_ERRORS
                        $stop(1);
                        `endif
                        end

                        // note: to allow for this it would be required to add if(stats_clear) into state HOSTRD,
                        //       but this then would also mean that the host read may return arbitrary data... 
                        //       not really good to do !
                        
                end

        end
end

// synthesis translate_on
// synopsys translate_on

`endif

endmodule // module stats_memarbs

